import json
import secrets
import hashlib
from typing import Any

from fastapi import Request

from app.database.postgre import SessionDep, select, col
from app.database.redis import get_card_token_redis, get_api_nonce_redis
from app.database.models import (App, Card, Device, Version, DeviceLog, TrialRecord, FirewallRule, RechargeLog,
                                 CardStatus, DeviceLogEventType, FirewallRuleType, AppLoginTimes)
from app.utils.uuid import verify_app_fk, generate_token
from app.utils.sign import generate_request_sign, generate_response_sign
from app.utils.time import get_now_date, get_offset_date, in_time_range, get_seconds_timedelta
from app.utils.types import safe_loads_json
from app.utils.ip import get_real_ip
from app.models import responses
from app.models.api import (CardLoginJSON, CardLogoutJSON, VerifyRequestSign, CardHeartbeatParams,
                            CardUnbindJSON, CardRechargeJSON, CardInfoParams, AppCheckUpdateParams,
                            TrialLoginJSON, TrialHeartbeatParams, UpdateExtraDataJSON)
from app.config import get_settings


def create_error_device_log(device_code: str, device_ip: str, detail: str, app_id: str, session: SessionDep):
    device_log = DeviceLog(
        device_code=device_code,
        device_ip=device_ip,
        event_type=DeviceLogEventType.ERROR.value,
        event_detail=detail,
        app_id=app_id
    )
    session.add(device_log)
    session.commit()
    session.refresh(device_log)
    return device_log


async def get_app_by_flag(app_flag: str, session: SessionDep):
    app = session.exec(select(App).where(App.flag == app_flag)).first()
    if not app:
        raise responses.FailureResponseError(10101)

    app_key = app.key
    verify_result = verify_app_fk(app.account_id, app.fk_version, app_flag, app_key)
    if not verify_result:
        raise responses.FailureResponseError(10102)

    return app


async def check_request_sign(data: VerifyRequestSign, session: SessionDep, request: Request):
    """验证请求头、请求签名与 APP FLAG"""
    timestamp = data.timestamp
    now = int(get_now_date().timestamp())
    if not (now - 60 <= timestamp <= now + 60):
        raise responses.FailureResponseError(10002)

    caller = request.headers.get('caller', '')
    if caller != hashlib.md5(data.app_flag.encode()).hexdigest():
        raise responses.FailureResponseError(10004)

    sign = data.sign
    timestamp_redis = get_api_nonce_redis()
    if await timestamp_redis.get(sign):
        raise responses.FailureResponseError(10003)
    else:
        await timestamp_redis.setex(sign, 5, 'nonce')

    params = data.model_dump(exclude={'sign'})
    app = await get_app_by_flag(data.app_flag, session)

    if app.force_disabled or app.is_disabled:
        raise responses.FailureResponseError(10103)

    endpoint = request.scope.get('path', '')
    right_sign = generate_request_sign(endpoint, request.method, params, app.flag, app.key)
    if not secrets.compare_digest(sign.encode(), right_sign.encode()):
        raise responses.FailureResponseError(10003)

    return app


def attach_response_sign(resp_data: dict, app_flag: str, app_key: str):
    sign = generate_response_sign(resp_data, app_flag, app_key)
    resp_data['sign'] = sign
    return resp_data


async def check_firewall_rule(app: App, session: SessionDep, request: Request, *, device_code: str = None):
    """检查设备 IP 或者设备码是否匹配防火墙规则"""
    device_ip = get_real_ip(request)

    # 检查 IP 是否在防火墙规则内
    rule1 = session.query(FirewallRule).filter(
        FirewallRule.app_id == app.id,
        FirewallRule.rule_match == device_ip,
        FirewallRule.rule_type == FirewallRuleType.IP.value,
        FirewallRule.is_enabled
    ).first()
    if rule1:
        raise responses.FailureResponseError(-1)

    # 检查设备是否在防火墙规则内
    if device_code:
        rule2 = session.query(FirewallRule).filter(
            FirewallRule.app_id == app.id,
            FirewallRule.rule_match == device_code,
            FirewallRule.rule_type == FirewallRuleType.DEVICE.value,
            FirewallRule.is_enabled
        ).first()
        if rule2:
            raise responses.FailureResponseError(-1)


async def handle_card_login(app: App, json_data: CardLoginJSON, session: SessionDep, request: Request):
    """处理卡密登录"""
    card_code = json_data.card
    device_code = json_data.device_code
    device_ip = get_real_ip(request)

    card_token_redis = get_card_token_redis()

    # 获取登录卡密
    card = session.query(Card).filter(Card.app_id == app.id, Card.card == card_code).first()
    if not card:
        return responses.Response10201()  # 登录卡密不存在

    # 检查是否在时间段
    time_range = app.time_restriction if card.use_app_config else card.time_restriction
    if not in_time_range(time_range):
        return responses.Response10206()  # 不在限制时间段登录

    # 根据卡密状态进行判断
    card_status = card.status

    if card_status == CardStatus.FREEZE.value:
        create_error_device_log(device_code, device_ip, f'卡密 {card_code} 请求登录，但是卡密已被冻结', app.id, session)
        return responses.Response10202()  # 登录卡密已被冻结
    elif card_status == CardStatus.EXPIRED.value:
        create_error_device_log(device_code, device_ip, f'卡密 {card_code} 请求登录，但是卡密已过期', app.id, session)
        return responses.Response10203(data={
            'expire_text': card.expire_time.strftime('%Y-%m-%d %H:%M:%S'),
            'expire_ts': int(card.expire_time.timestamp())
        })  # 登录卡密已过期
    elif card_status == CardStatus.UNKNOWN.value:
        card.activate_time = get_now_date()
        card.expire_time = get_offset_date(card.hours * 60 * 60)
    elif card_status == CardStatus.ACTIVE.value:
        device = session.query(Device).filter(
            Device.app_id == app.id, Device.code == device_code, Device.card_id == card.id).first()
        max_bind = app.max_bind if card.use_app_config else card.max_bind
        if not device and len(card.devices) >= max_bind:
            create_error_device_log(
                device_code, device_ip, f'卡密 {card_code} 请求登录，但是超过设备上限: {card.max_bind}', app.id,
                session)
            return responses.Response10205(data={"max_bind": card.max_bind})  # 超过设备上限，无法登录新的设备
    else:
        create_error_device_log(
            device_code, device_ip, f'卡密 {card_code} 请求登录，但是卡密状态未知: {card_status}', app.id, session)
        return responses.Response10208()  # 卡密状态未知，无法登录

    # 生成 token 并记录
    token = generate_token({
        'app_id': app.id,
        'card': card_code,
        'device': device_code
    }, get_settings().CARD_TOKEN_SECRET_KEY)
    await card_token_redis.setex(token, app.ping_duration * 60, json.dumps({
        'app_id': app.id,
        'card_id': card.id,
        'remain': int(card.expire_time.timestamp() - get_now_date().timestamp())
    }))

    # 构建设备的登录
    device = session.query(Device).filter(
        Device.app_id == app.id, Device.code == device_code, Device.card_id == card.id).first()
    if not device:
        device = Device(code=device_code, app_id=app.id, card_id=card.id)
    device.login_time = get_now_date()
    device.ping_time = get_now_date()
    device.ping_ip = device_ip
    device.token = token
    session.add(device)

    # 记录设备日志
    device_log = DeviceLog(
        device_code=device_code,
        device_ip=device_ip,
        event_type=DeviceLogEventType.LOGIN.value,
        event_detail=f'卡密 {card_code} 登录成功，token: {token}',
        app_id=app.id
    )
    session.add(device_log)

    # 记录登录时间
    login_time = AppLoginTimes(app_id=app.id)
    session.add(login_time)

    session.commit()
    session.refresh(card)
    session.refresh(device)
    session.refresh(device_log)

    response_data = attach_response_sign({
        'token': token,
        'card_type': card.type,
        'expire_text': card.expire_time.strftime('%Y-%m-%d %H:%M:%S'),
        'expire_ts': int(card.expire_time.timestamp()),
        'server_ts': int(get_now_date().timestamp()),
        'extra_data': card.extra_data
    }, app.flag, app.key)

    return responses.SuccessResponse(msg='登录成功', data=response_data)


async def handle_card_heartbeat(app: App, query_params: CardHeartbeatParams, session: SessionDep, request: Request):
    """处理卡密心跳"""
    device_code = query_params.device_code
    device_ip = get_real_ip(request)

    # 获取 token 数据
    card_token_redis = get_card_token_redis()
    token_json = await card_token_redis.get(query_params.token)
    token_data = safe_loads_json(token_json, {})
    if not token_data or token_data.get('app_id') != app.id:
        return responses.Response10601()  # 未登录或者应用不匹配

    # 获取卡密
    card = session.get(Card, token_data.get('card_id'))
    if not card or card.card != query_params.card:
        return responses.Response10201()  # 登录的卡密与传入的卡密不一致

    # 检查是否在时间段
    time_range = app.time_restriction if card.use_app_config else card.time_restriction
    if not in_time_range(time_range):
        return responses.Response10206()  # 不能在非规定时间段内进行心跳

    # 根据卡密状态进行判断
    card_status = card.status

    if card_status == CardStatus.FREEZE.value:
        return responses.Response10202()  # 心跳的卡密已经冻结
    elif card_status == CardStatus.EXPIRED.value:
        return responses.Response10203(data={
            'expire_text': card.expire_time.strftime('%Y-%m-%d %H:%M:%S'),
            'expire_ts': int(card.expire_time.timestamp())
        })  # 心跳的卡密已经过期
    elif card_status == CardStatus.UNKNOWN.value:
        return responses.Response10209()  # 心跳的卡密是未激活的，无法进行心跳

    # 获取已存在设备
    device = session.query(Device).filter(
        Device.app_id == app.id, Device.code == device_code, Device.card_id == card.id).first()
    if not device:
        return responses.Response10304()  # 心跳的设备并不存在（当前心跳非已绑定/已登录设备）

    if device.token != query_params.token:
        return responses.Response10601()  # 心跳设备的 Token 无法匹配

    if get_now_date().timestamp() - device.ping_time.timestamp() < 29:
        return responses.Response10402()  # 心跳时间过短，低于预设 30s

    # 更新设备心跳记录与 token 有效时间
    device.ping_time = get_now_date()
    device.ping_ip = device_ip
    await card_token_redis.setex(query_params.token, app.ping_duration * 60, json.dumps({
        'app_id': app.id,
        'card_id': card.id,
        'remain': int(card.expire_time.timestamp() - get_now_date().timestamp())
    }))
    session.commit()
    session.refresh(device)

    # 构建响应数据
    response_data = attach_response_sign({
        'type': 'card',
        'expire_text': card.expire_time.strftime('%Y-%m-%d %H:%M:%S'),
        'expire_ts': int(card.expire_time.timestamp()),
        'server_ts': int(get_now_date().timestamp()),
        'extra_data': card.extra_data
    }, app.flag, app.key)

    return responses.SuccessResponse(msg='心跳成功', data=response_data)


async def handle_card_logout(json_data: CardLogoutJSON):
    """处理卡密退出登录"""
    card_token_redis = get_card_token_redis()
    token_json = await card_token_redis.get(json_data.token)
    if token_json:
        await card_token_redis.delete(json_data.token)

    return responses.SuccessResponse(msg='退出登录成功')


async def handle_card_unbind(app: App, json_data: CardUnbindJSON, session: SessionDep):
    """处理卡密解绑"""
    device_code = json_data.device_code

    # 获取 token 数据
    card_token_redis = get_card_token_redis()
    token_json = await card_token_redis.get(json_data.token)
    token_data = safe_loads_json(token_json, {})
    if not token_data or token_data.get('app_id') != app.id:
        return responses.Response10601()  # 应用匹配不上或未登录

    # 获取卡密
    card = session.get(Card, token_data.get('card_id'))
    if not card or card.card != json_data.card:
        return responses.Response10201()  # 卡密不存在

    # 检查是否允许解绑
    allow_unbind = app.unbind_enabled if card.use_app_config else card.unbind_enabled
    if not allow_unbind:
        return responses.Response10301()  # 不允许解绑

    # 根据卡密状态进行判断
    card_status = card.status

    if card_status == CardStatus.FREEZE.value:
        return responses.Response10302()  # 已冻结无法解绑
    elif card_status == CardStatus.EXPIRED.value:
        return responses.Response10303()  # 已过期无法解绑
    elif card_status == CardStatus.UNKNOWN.value:
        return responses.Response10209()  # 未激活无法解绑

    # 获取已存在设备
    device = session.query(Device).filter(
        Device.app_id == app.id, Device.code == device_code, Device.card_id == card.id).first()
    if not device:
        return responses.Response10304()  # 并非绑定的设备

    if device.token != json_data.token:
        return responses.Response10601()  # 登录的 Token 值对应不上

    unbind_cost_seconds = (app.unbind_cost if card.use_app_config else card.unbind_cost) * 60
    if unbind_cost_seconds:
        new_expire_time = card.expire_time - get_seconds_timedelta(unbind_cost_seconds)
        if new_expire_time.timestamp() - get_now_date().timestamp() <= 0:
            return responses.Response10305()  # 剩余时长不足以解绑
        card.expire_time = new_expire_time

    await card_token_redis.delete(json_data.token)
    session.delete(device)
    session.commit()
    session.refresh(card)

    return responses.SuccessResponse(msg='解绑成功')


async def handle_card_recharge(app: App, json_data: CardRechargeJSON, session: SessionDep, request: Request):
    """处理以卡充卡"""
    # 获取充值地 IP
    recharge_ip = get_real_ip(request)

    # 检查是否允许充值
    allow_recharge = app.allow_recharge
    if not allow_recharge:
        return responses.Response10207()  # 应用不允许充值卡密

    # 获取需要充值的卡密
    old_card = session.query(Card).filter(Card.app_id == app.id, Card.card == json_data.old_card).first()
    if not old_card:
        return responses.Response10201()  # 需要充值的卡密不存在

    # 根据卡密状态进行判断
    old_card_status = old_card.status

    if old_card_status == CardStatus.FREEZE.value:
        return responses.Response10202()  # 需要充值的卡密被冻结
    elif old_card_status == CardStatus.EXPIRED.value:
        return responses.Response10203(data={
            'expire_text': old_card.expire_time.strftime('%Y-%m-%d %H:%M:%S'),
            'expire_ts': int(old_card.expire_time.timestamp())
        })  # 需要充值的卡密已经过期
    elif old_card_status == CardStatus.UNKNOWN.value:
        return responses.Response10209()  # 需要充值的卡密是未激活状态

    # 获取被消耗的卡密
    new_card = session.query(Card).filter(Card.app_id == app.id, Card.card == json_data.new_card).first()
    if not new_card:
        return responses.Response10201()  # 被消耗的卡密不存在

    if new_card.status != CardStatus.UNKNOWN.value:
        return responses.Response10204()  # 被消耗的卡密不是未激活状态

    # 检查新旧卡密的绑定数量是否一致
    if old_card.max_bind != new_card.max_bind:
        return responses.Response10210()  # 被消耗的卡密与需要充值的卡密设备上限不一致

    # 计算卡密之间的时间
    old_time = old_card.expire_time
    new_time = old_card.expire_time + get_seconds_timedelta(new_card.hours * 60 * 60)

    old_card.expire_time = new_time
    new_card.activate_time = get_now_date()
    new_card.expire_time = get_now_date()

    # 构建充值日志
    device_log = RechargeLog(
        app_id=app.id,
        origin_card=old_card.card,
        used_card=new_card.card,
        recharge_ip=recharge_ip,
        old_time=old_time,
        new_time=new_time
    )
    session.add(device_log)

    session.commit()
    session.refresh(old_card)
    session.refresh(new_card)

    response_data = {
        'expire_text': old_card.expire_time.strftime('%Y-%m-%d %H:%M:%S'),
        'expire_ts': int(old_card.expire_time.timestamp()),
        'server_ts': int(get_now_date().timestamp()),
        'extra_data': old_card.extra_data
    }

    return responses.SuccessResponse(msg='充值成功', data=response_data)


async def get_card_info(app: App, query_params: CardInfoParams, session: SessionDep):
    """获取卡密信息"""
    # 获取卡密
    card = session.query(Card).filter(Card.app_id == app.id, Card.card == query_params.card).first()
    if not card:
        return responses.Response10201()  # 卡密不存在

    response_data = card.model_dump(include={'type', 'hours', 'activate_time', 'expire_time', 'extra_data'})
    response_data['time_restriction'] = app.time_restriction if card.use_app_config else app.time_restriction
    response_data['max_bind'] = app.max_bind if card.use_app_config else app.max_bind
    response_data['unbind_enabled'] = app.unbind_enabled if card.use_app_config else app.unbind_enabled
    response_data['unbind_cost'] = app.unbind_cost if card.use_app_config else app.unbind_cost
    response_data['status'] = card.status

    return responses.SuccessResponse(data=response_data)


async def get_app_config(app: App):
    """获取应用配置"""
    response_data = app.model_dump(
        include={'name', 'notice', 'max_trial', 'trial_duration', 'ping_duration', 'extra_data'})
    return responses.SuccessResponse(data=response_data)


async def get_app_notice(app: App):
    """获取应用公告"""
    response_data = app.model_dump(include={'notice'})
    return responses.SuccessResponse(data=response_data)


async def get_app_extra_data(app: App):
    """获取应用额外数据"""
    response_data = app.model_dump(include={'extra_data'})
    return responses.SuccessResponse(data=response_data)


async def check_app_update(app: App, query_params: AppCheckUpdateParams, session: SessionDep):
    """检查是否有更新"""
    # 获取当前客户端版本
    current_version = query_params.version

    # 查询比当前版本大的最新版本（按版本号降序取第一个）
    newer_version = session.query(Version).filter(
        Version.app_id == app.id,
        col(Version.is_publish).is_(True),
        Version.version_code > current_version
    ).order_by(col(Version.version_code).desc()).first()

    # 组织响应数据
    version_data = {}
    if newer_version:
        version_data = {
            'code': newer_version.version_code,
            'url': newer_version.download_url,
            'description': newer_version.description,
            'force': newer_version.is_enforce,
            'md5': newer_version.file_md5,
            'create_time': newer_version.create_time.strftime('%Y-%m-%d %H:%M:%S'),
            'update_time': newer_version.update_time.strftime('%Y-%m-%d %H:%M:%S'),
        }

    response_data = {
        'has_update': bool(newer_version),
        'version': version_data,
        'extra_data': newer_version.extra_data if newer_version else {}
    }

    return responses.SuccessResponse(data=response_data)


async def handle_trial_login(app: App, json_data: TrialLoginJSON, session: SessionDep, request: Request):
    """处理设备试用登录"""
    device_code = json_data.device_code
    device_ip = get_real_ip(request)

    # 获取应用允许的最大试用次数与单次试用时间
    max_trial = app.max_trial
    trial_duration = app.trial_duration * 60
    if max_trial <= 0 or trial_duration <= 0:
        return responses.Response10501()  # 应用不允许试用

    # 如果记录已存在，进行检查并更新
    record = session.query(TrialRecord).filter(
        TrialRecord.app_id == app.id, TrialRecord.device_code == device_code).first()
    if record:
        record.request_count += 1
        if get_now_date().timestamp() > record.invalid_time.timestamp():
            if record.trial_count >= max_trial:
                session.commit()
                session.refresh(record)
                return responses.Response10502()  # 发现过期后，检查使用次数已达上限

            record.trial_count += 1
            record.trial_ip = device_ip
            record.trial_time = get_now_date()
            record.invalid_time = get_offset_date(trial_duration)
            session.commit()
            session.refresh(record)
    else:  # 记录不存在，创建一条新的试用记录
        record = TrialRecord(
            app_id=app.id,
            device_code=device_code,
            trial_ip=device_ip,
            trial_time=get_now_date(),
            invalid_time=get_offset_date(trial_duration),
            request_count=1,
            trial_count=1
        )
        session.add(record)
        session.commit()
        session.refresh(record)

    # 构建响应数据
    response_data = attach_response_sign({
        'card_type': '试用卡',
        'expire_text': record.invalid_time.strftime('%Y-%m-%d %H:%M:%S'),
        'expire_ts': int(record.invalid_time.timestamp()),
        'server_ts': int(get_now_date().timestamp())
    }, app.flag, app.key)

    return responses.SuccessResponse(msg='登录成功', data=response_data)


async def handle_trial_heartbeat(app: App, query_params: TrialHeartbeatParams, session: SessionDep):
    """处理设备试用心跳"""
    device_code = query_params.device_code

    # 查找当前设备的试用记录
    record = session.query(TrialRecord).filter(
        TrialRecord.app_id == app.id, TrialRecord.device_code == device_code).first()

    if not record:
        return responses.Response10504()  # 试用记录不存在，需要重新登录

    if get_now_date().timestamp() > record.invalid_time.timestamp():
        return responses.Response10503()  # 本次试用已到期

    # 构建响应数据
    response_data = attach_response_sign({
        'type': 'trial',
        'expire_text': record.invalid_time.strftime('%Y-%m-%d %H:%M:%S'),
        'expire_ts': int(record.invalid_time.timestamp()),
        'server_ts': int(get_now_date().timestamp()),
        'extra_data': {}
    }, app.flag, app.key)

    return responses.SuccessResponse(msg='心跳成功', data=response_data)


def deep_filter_keys(data: Any) -> Any:
    """ 递归过滤所有以 $ 开头的键 """
    if isinstance(data, dict):
        # 使用字典推导式过滤当前层级
        filtered = {
            k: deep_filter_keys(v)
            for k, v in data.items()
            if not k.startswith('$')
        }
        return filtered
    else:
        # 基础类型直接返回
        return data


async def handle_update_card_extra_data(app: App, json_data: UpdateExtraDataJSON, session: SessionDep):
    # 获取 token 数据
    card_token_redis = get_card_token_redis()
    token_json = await card_token_redis.get(json_data.token)
    token_data = safe_loads_json(token_json, {})
    if not token_data or token_data.get('app_id') != app.id:
        return responses.Response10601()  # 应用匹配不上或未登录

    # 获取卡密
    card = session.get(Card, token_data.get('card_id'))
    if not card or card.card != json_data.card:
        return responses.Response10201()  # 卡密不存在

    old_extra_data = dict(card.extra_data)
    old_extra_data.update(deep_filter_keys(json_data.extra_data))
    Card.sqlmodel_update(card, {
        'extra_data': old_extra_data
    })

    session.commit()
    session.refresh(card)

    response_data = card.model_dump(include={'type', 'hours', 'activate_time', 'expire_time', 'extra_data'})
    response_data['time_restriction'] = app.time_restriction if card.use_app_config else app.time_restriction
    response_data['max_bind'] = app.max_bind if card.use_app_config else app.max_bind
    response_data['unbind_enabled'] = app.unbind_enabled if card.use_app_config else app.unbind_enabled
    response_data['unbind_cost'] = app.unbind_cost if card.use_app_config else app.unbind_cost
    response_data['status'] = card.status

    return responses.SuccessResponse(msg='更新成功', data=response_data)

